/**
 * 
 */
package qy.jalgotrade.dataseries;

import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import qy.jalgotrade.event.Event;
import qy.jalgotrade.utils.FastUtilDoubleArrayListDeque;
import qy.jalgotrade.utils.FastUtilIntArrayListDeque;
import qy.jalgotrade.utils.FastUtilLongArrayListDeque;
import qy.jalgotrade.utils.ListDeque;
import qy.jalgotrade.utils.PySpecs;

/**
 * A DataSeries that holds values in a sequence in memory.<br />
 * pyalgotrade.dataseries.SequenceDataSeries: 在内存中以 sequence 形式存储 DataSeries.
 * 
 * @author qy
 *
 */
public class SequenceDataSeries<T> extends DataSeries<T> {

	/**
	 * 
	 * @param <E>
	 * @param constVal
	 * @param length
	 * @return
	 * @throws Exception
	 */
	public static <E> SequenceDataSeries<E> makeConstSequenceDataseries(E constVal, int length) throws Exception {

		return makeConstSequenceDataseries(constVal, length, Object.class, 0);
	}

	/**
	 * 
	 * @param <E>
	 * @param constVal
	 * @param length
	 * @param elemType
	 * @return
	 * @throws Exception
	 */
	public static <E> SequenceDataSeries<E> makeConstSequenceDataseries(E constVal, int length, Class<?> elemType)
			throws Exception {

		return makeConstSequenceDataseries(constVal, length, elemType, 0);
	}

	/**
	 * 
	 * @param <E>
	 * @param constVal
	 * @param length
	 * @param elemType
	 * @param maxLen
	 * @return
	 * @throws Exception
	 */
	public static <E> SequenceDataSeries<E> makeConstSequenceDataseries(E constVal, int length, Class<?> elemType,
			int maxLen) throws Exception {

		SequenceDataSeries<E> ret = new SequenceDataSeries<>(maxLen, elemType);
		assert length <= ret.getMaxLen();
		for (int i = 0; i < length; i++) {
			ret.append(constVal);
		}

		return ret;
	}

	private Event __newValueEvent;

	private ListDeque<T> __values;

	private boolean __elementOptimize;

	private Class<?> __elementType;

	private PySpecs.SeqGetItem<?> __primitiveValues;

	private ListDeque<ZonedDateTime> __dateTimes;

	/**
	 * 
	 * @throws Exception
	 */
	public SequenceDataSeries() throws Exception {

		this(0, Object.class);
	}

	/**
	 * 
	 * @param maxLen The maximum number of values to hold. Once a bounded length is full, when new items
	 *               are added, a corresponding number of items are discarded from the opposite end. If
	 *               None then dataseries.DEFAULT_MAX_LEN is used.
	 * @throws Exception
	 */
	public SequenceDataSeries(int maxLen) throws Exception {

		this(maxLen, Object.class);
	}

	/**
	 * 
	 * @param elemType For perf. optimization
	 * @throws Exception
	 */
	public SequenceDataSeries(Class<?> elemType) throws Exception {

		this(0, elemType);
	}

	/**
	 * 
	 * @param maxLen   The maximum number of values to hold. Once a bounded length is full, when new
	 *                 items are added, a corresponding number of items are discarded from the opposite
	 *                 end. If None then dataseries.DEFAULT_MAX_LEN is used.
	 * @param elemType For perf. optimization
	 * @throws Exception
	 */
	public SequenceDataSeries(int maxLen, Class<?> elemType) throws Exception {

		super();

		maxLen = DataSeries.getCheckedMaxLen(maxLen);

		__newValueEvent = new Event("dataseries-new-value-event");

		__elementOptimize = false;
		__values = null;
		__primitiveValues = null;
		if (Integer.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Integer.class;
			__primitiveValues = new FastUtilIntArrayListDeque(maxLen);
		} else if (Long.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Long.class;
			__primitiveValues = new FastUtilLongArrayListDeque(maxLen);
		} else if (Double.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Double.class;
			__primitiveValues = new FastUtilDoubleArrayListDeque(maxLen);
		} else {
			// Object.class.equals(elemType)
			__elementType = Object.class;
			__values = new ListDeque<>(maxLen);
		}

		__dateTimes = new ListDeque<>(maxLen);
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.dataseries.DataSeries#__len__()
	 */
	@Override
	public int __len__() {

		if (__elementOptimize) {
			return __primitiveValues.__len__();
		} else {
			return __values.__len__();
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.dataseries.DataSeries#__getitem__(int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T __getitem__(int idx) {

		if (__elementOptimize) {
			if (idx < 0) {
				idx += __primitiveValues.__len__();
			}
			if (Integer.class.equals(__elementType)) {
				return (T) ((FastUtilIntArrayListDeque) __primitiveValues).__getitem__(idx);
			} else if (Long.class.equals(__elementType)) {
				return (T) ((FastUtilLongArrayListDeque) __primitiveValues).__getitem__(idx);
			} else {
				// Double.class.equals(__elementType)
				return (T) ((FastUtilDoubleArrayListDeque) __primitiveValues).__getitem__(idx);
			}
		} else {
			return __values.__getitem__(idx);
		}
	}

	/**
	 * Sets the maximum number of values to hold and resizes accordingly if necessary.
	 * 
	 * @param maxLen
	 */
	public void setMaxLen(int maxLen) {

		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {
				((FastUtilIntArrayListDeque) __primitiveValues).resize(maxLen);
			} else if (Long.class.equals(__elementType)) {
				((FastUtilLongArrayListDeque) __primitiveValues).resize(maxLen);
			} else {
				// Double.class.equals(__elementType)
				((FastUtilDoubleArrayListDeque) __primitiveValues).resize(maxLen);
			}
		} else {
			__values.resize(maxLen);
		}
		__dateTimes.resize(maxLen);
	}

	/**
	 * Returns the maximum number of values to hold.
	 * 
	 * @return
	 */
	public int getMaxLen() {

		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {
				return ((FastUtilIntArrayListDeque) __primitiveValues).getMaxLen();
			} else if (Long.class.equals(__elementType)) {
				return ((FastUtilLongArrayListDeque) __primitiveValues).getMaxLen();
			} else {
				// Double.class.equals(__elementType)
				return ((FastUtilDoubleArrayListDeque) __primitiveValues).getMaxLen();
			}
		} else {
			return __values.getMaxLen();
		}
	}

	/**
	 * <pre>
	 * Event handler receives:
	 * 1: Dataseries generating the event
	 * 2: The datetime for the new value
	 * 3: The new value
	 * </pre>
	 * 
	 * @return
	 */
	public Event getNewValueEvent() {

		return __newValueEvent;
	}

	/**
	 * 
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T getValueAbsolute(int pos) {

		T ret = null;
		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {
				if (pos >= 0 && pos < __primitiveValues.__len__()) {
					ret = (T) ((FastUtilIntArrayListDeque) __primitiveValues).__getitem__(pos);
				}
			} else if (Long.class.equals(__elementType)) {
				if (pos >= 0 && pos < __primitiveValues.__len__()) {
					ret = (T) ((FastUtilLongArrayListDeque) __primitiveValues).__getitem__(pos);
				}
			} else {
				// Double.class.equals(__elementType)
				if (pos >= 0 && pos < __primitiveValues.__len__()) {
					ret = (T) ((FastUtilDoubleArrayListDeque) __primitiveValues).__getitem__(pos);
				}
			}
		} else {
			if (pos >= 0 && pos < __values.__len__()) {
				ret = __values.__getitem__(pos);
			}
		}
		return ret;
	}

	/**
	 * Appends a value.
	 * 
	 * @param value
	 * @throws Exception
	 */
	public void append(T value) throws Exception {

		appendWithDateTime(null, value);
	}

	/**
	 * <pre>
	 * Appends a value with an associated datetime.
	 * 
	 * .. note::
	 *     If dateTime is not None, it must be greater than the last one.
	 * </pre>
	 * 
	 * @param dateTime
	 * @param value
	 * @throws Exception
	 */
	@Override
	public void appendWithDateTime(ZonedDateTime dateTime, T value) throws Exception {

		if (dateTime != null && __dateTimes.__len__() != 0 && __dateTimes.__getitem__(-1).compareTo(dateTime) >= 0) {
			throw new Exception(String.format("Invalid datetime (%s). It must be bigger than that last one (%s)",
					dateTime, __dateTimes.__getitem__(-1)));
		}

		if (__elementOptimize) {

			assert ((PySpecs.SeqGetItem<?>) __primitiveValues).__len__() == __dateTimes.__len__();
			__dateTimes.append(dateTime);
			if (Integer.class.equals(__elementType)) {

				((FastUtilIntArrayListDeque) __primitiveValues).append((Integer) value);

			} else if (Long.class.equals(__elementType)) {

				((FastUtilLongArrayListDeque) __primitiveValues).append((Long) value);

			} else {
				// Double.class.equals(__elementType)
				((FastUtilDoubleArrayListDeque) __primitiveValues).append((Double) value);
			}
		} else {

			assert __values.__len__() == __dateTimes.__len__();
			__dateTimes.append(dateTime);
			__values.append(value);
		}

		Map<String, Object> params = new HashMap<>();
		params.put("dataSeries", this);
		params.put("dateTime", dateTime);
		params.put("value", value);

		Event event = getNewValueEvent();
		event.emit(event.getName(), params);
	}

	@Override
	public List<ZonedDateTime> getDateTimes() {

		return __dateTimes.data();
	}
}
